home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995 February: Tool Chest / Dev.CD Feb 95 / Dev.CD Feb 95.toast / Sample Code / Pascal Sample 3.0B10 / Source / Print.inc1.p < prev    next >
Encoding:
Text File  |  1993-10-13  |  18.1 KB  |  497 lines  |  [TEXT/MPS ]

  1. (******************************************************************************
  2. *
  3. *    Apple Macintosh Developer Technical Support
  4. *
  5. *    Code for the printing routines
  6. *
  7. *    Program:    Sample 3.0
  8. *    FILE:        Print.inc1.p - Pascal implementation
  9. *
  10. *    by:            Matt Deatherage
  11. *
  12. *    Copyright © 1988-1993 Apple Computer, Inc.
  13. *    All rights reserved.
  14. *
  15. *******************************************************************************
  16.  
  17. (*******************************************************************************
  18. * Types
  19. *******************************************************************************)
  20.  
  21. TYPE
  22.  
  23. (*******************************************************************************
  24. * HeightAndWidth is a record containing page counts -- how many pieces of paper
  25. * do we have to use to make this entire document print?  We print in a
  26. * rectangular array, and this record tells us how many pages wide and tall
  27. * that array has to be.
  28. *******************************************************************************)
  29.  
  30.     HeightAndWidth = RECORD
  31.         height: INTEGER;
  32.         width: INTEGER;
  33.         END;
  34.  
  35. (*******************************************************************************
  36. * Private global variables maintained by this unit. These variables are
  37. * used across printing functions and are here because, while they're not
  38. * the business of code outside this unit, they also don't need to be passed
  39. * as parameters to every routine in here.
  40. *******************************************************************************)
  41.  
  42. VAR
  43.     theStatus: TPrStatus;            { a Status record for our printing loop }
  44.     pageDims: HeightAndWidth;        { dimensions of the page we're using }
  45.     documentPicture: PicHandle;        { a PICT of what we're printing }
  46.  
  47. (*******************************************************************************
  48. * This routine is referenced here so that we can unload its segments without
  49. * having to USE the entire UNIT it's contained in.
  50. *******************************************************************************)
  51.  
  52. FUNCTION DoCircleOptions(VAR circle: CircleRec): BOOLEAN;
  53.     EXTERNAL;
  54.  
  55. {$S Print}
  56. (******************************************************************************
  57. *
  58. * private: GetPages
  59. *
  60. * GetPages takes a rectangle representing the size of what we're trying to
  61. * print, and a print record (which tells us how big the paper we've got is),
  62. * and returns a HeightAndWidth record which tells us how many pages it
  63. * takes to print this document.
  64. *
  65. * We calculate how many pixels wide (or tall) the document is, then how
  66. * many pixels wide (or tall) the imageable area is.  Dividing the document
  67. * dimension by the page dimension shows how many pages (in that direction)
  68. * the document requires, incremented if there's any remainder indicating
  69. * more stuff on the next page.
  70. *
  71. ******************************************************************************)
  72.  
  73. FUNCTION GetPages(theDocBounds: Rect; thePrintRecord: THPrint): HeightAndWidth;
  74.  
  75. VAR
  76.     pageHeight,                { the height of the imageable paper area }
  77.     docHeight,                { the height of the document we're printing }
  78.     pageWidth,                { the width of the imageable paper area }
  79.     docWidth,                { the width of the document we're printing }
  80.     height,                    { how many pages across this document takes }
  81.     width: INTEGER;            { how many pages down this document takes }
  82.  
  83. BEGIN
  84.     WITH theDocBounds DO
  85.         docWidth := right - left;            { width of the document }
  86.     WITH thePrintRecord^^.prInfo.rPage DO
  87.         pageWidth := right - left;            { width of the imageable area }
  88.  
  89.     width := docWidth DIV pageWidth;        { Whole # of pages required }
  90.     IF docWidth MOD pageWidth <> 0 THEN        { If there's any left over, then }
  91.         width := width + 1;                    { add another page to hold it }
  92.  
  93.     WITH theDocBounds DO
  94.         docHeight := bottom - top;            { height of the document }
  95.     WITH thePrintRecord^^.prInfo.rPage DO
  96.         pageHeight := bottom - top;            { height of the document }
  97.  
  98.     height := docHeight DIV pageHeight;        { Whole # of pages required }
  99.     IF docHeight MOD pageHeight <> 0 THEN    { If there's any left over, then }
  100.         height := height + 1;                { add another page to hold it }
  101.  
  102.     GetPages.width := width;                { fill in the record to return }
  103.     GetPages.height := height;                { the values we just calculated }
  104.  
  105. END; { GetPages }
  106.  
  107. {$S Print}
  108. (******************************************************************************
  109. *
  110. * private: DetermineRealNumberOfPagesInDoc
  111. *
  112. * This routine is part of Luke's Technical Note, and returns an INTEGER
  113. * saying how many sheets of paper we require.  We're passed a rectangle
  114. * indicating the size of the document or Picture we're printing, and a print
  115. * record.  We pass those through to GetPages, and return the height
  116. * multiplied by the width.  It would be easy enough to fold this routine
  117. * and GetPages together, but this division more closely follows the
  118. * routines as defined in the Technical Note.
  119. ******************************************************************************)
  120.  
  121. FUNCTION DetermineRealNumberOfPagesInDoc(documentSize: Rect;
  122.                                          printRecord: THPrint): INTEGER;
  123.  
  124. BEGIN
  125.     pageDims := GetPages(documentSize, printRecord);
  126.     DetermineRealNumberOfPagesInDoc := pageDims.height * pageDims.width;
  127. END;
  128.  
  129. {$S Print}
  130. (******************************************************************************
  131. *
  132. * private: PostPrintingErrors
  133. *
  134. * This procedure alerts the user that something went wrong during printing.
  135. * As long as the error isn't "The user canceled printing," we use NumToString
  136. * and ParamText to set up an ASCII version of the number as "^0".  We
  137. * then call our utility routine AlertUser, which presents the error to the
  138. * user.
  139. ******************************************************************************)
  140.  
  141. PROCEDURE PostPrintingErrors(theError: OSErr);
  142.  
  143. VAR
  144.     theErrorString: Str255;            { String to hold ASCII number.  Must be
  145.                                       Str255 because it's a formal VAR
  146.                                       parameter. }
  147.  
  148. BEGIN
  149.     IF theError <> iPrAbort THEN    { make sure the user didn't cancel }
  150.         BEGIN
  151.             NumToString(LONGINT(theError), theErrorString);
  152.             ParamText(theErrorString, '', '', '');        
  153.             AlertUser(rPrintingError);
  154.         END;
  155. END; { PostPrintingErrors }
  156.  
  157. {$S Print}
  158. (******************************************************************************
  159. *
  160. * private: DrawStuff
  161. *
  162. * in some applications, this would be a call to the same routine that draws
  163. * your windows.  in this application, we always print a QuickDraw PICT so
  164. * we leave this routine private.
  165. *
  166. * DrawStuff draws page number pageNumber of thePicture.  thePage lets us
  167. * offset the Picture so we can draw the right portion of a multi-page PICT.
  168. * We set the port to thePort before drawing.
  169. *
  170. * The offsetting algorithm isn't obvious; let's look at it a bit.  First,
  171. * the original Picture frame (the size of the whole Picture) is copied into
  172. * the offsetPage rectangle.  To make DrawPicture draw the Picture offset
  173. * onto the page we want, but without scaling it, we keep the same rectangle
  174. * size but move it.  If we want to draw the second page across, we need
  175. * to shift the rectangle of the Picture left by one page width, so the
  176. * second page is the part starting at (0,0).
  177. *
  178. *
  179. * We arbitrarily decide to draw pages horizontally first and vertically
  180. * second, so now we can calculate a formula for offsetting.  Here's a
  181. * picture of a 12-page PICT we might be printing to help illustrate:
  182. *
  183. *  +-----+-----+-----+-----+
  184. *  |     |     |     |     |
  185. *  |  1  |  2  |  3  |  4  |
  186. *  |     |     |     |     |
  187. *  +-----+-----+-----+-----+
  188. *  |     |     |     |     |
  189. *  |  5  |  6  |  7  |  8  |
  190. *  |     |     |     |     |
  191. *  +-----+-----+-----+-----+
  192. *  |     |     |     |     |
  193. *  |  9  | 10  | 11  | 12  |
  194. *  |     |     |     |     |
  195. *  +-----+-----+-----+-----+
  196. * We calculate the amount to move the rectangle by taking the page number 
  197. * we're printing and dividing by the number of pages across in our page
  198. * dimensions (HeightAndWidth record), using the remainder.  In this example,
  199. * we're printing page #6.  Dividing 6 by 4 and taking the remainder gives
  200. * 2, giving us the second page in the row.  Multiplying that by the width
  201. * of a page gives us the amount to offset horizontally.  Almost.
  202. *
  203. * The trick is that the first page in each row doesn't need to be offset
  204. * horizontally (and the first page in each column doesn't need to be offset
  205. * vertically), and the fourth page needs to be offset by three pages, not
  206. * zero.  so before doing the division, we subtract one from the page number
  207. * to account for this.  Multiplying the result by the page width gives
  208. * the amount to offset, but since we need to move the picture left or up,
  209. * the offset is negative.  That gives this formula for horizontal offset:
  210. *
  211. * -((pageNumber - 1) MOD pageDims.width) * (thePage.right - thePage.left)
  212. *
  213. * and the vertical offset counterpart:
  214. *
  215. * -((pageNumber - 1) DIV pageDims.width) * (thePage.bottom - thePage.top)
  216. *
  217. * The vertical direction uses DIV and the width to give the row number, much
  218. * as MOD and the width gives the column number.
  219. *
  220. ******************************************************************************)
  221.  
  222. PROCEDURE DrawStuff(thePage: Rect; thePort: GrafPtr; pageNumber: INTEGER;
  223.                     thePicture: PicHandle);
  224.  
  225. VAR
  226.     offsetPage: Rect;            { copy of Picture frame rectangle }
  227.  
  228. BEGIN
  229.  
  230.     pageNumber := pageNumber - 1;    { to simplify calculations }
  231.     offsetPage := thePicture^^.picFrame;
  232.     OffsetRect(offsetPage,
  233.                -((pageNumber) MOD pageDims.width) * (thePage.right - thePage.left),
  234.                -((pageNumber) DIV pageDims.width) * (thePage.bottom - thePage.top));
  235.     SetPort(thePort);
  236.     DrawPicture(thePicture, offsetPage);
  237.  
  238. END; { DrawStuff }
  239.  
  240. {$S Print}
  241. (******************************************************************************
  242. *
  243. * Public: PageSetup
  244. *
  245. * PageSetup opens the Printing Manager and, if things are OK, presents the
  246. * standard style dialog if OKToInteract (in SampleUtilities) says that it's
  247. * OK to talk to the user.
  248. *
  249. * Although Sample doesn't require this, this routine (from the Technical Note)
  250. * preserves the current GrafPort because the Printing Manager may change it.
  251. *
  252. * Errors are not posted until the Printing Manager is closed.  More on this
  253. * is in the description for the Print routine.
  254. ******************************************************************************)
  255.  
  256. FUNCTION PageSetup(thePrRecHdl: THPrint): BOOLEAN;
  257.  
  258. VAR
  259.     printError: OSErr;                    { any error that may be returned }
  260.     oldPort: GrafPtr;                    { the GrafPort upon entry }
  261.     oldResFile: INTEGER;                { the Printing Manager's res file }
  262.  
  263. BEGIN
  264.     GetPort(oldPort);                    { save the current GrafPort }
  265.     oldResFile := CurResFile;            { save the resource file because the
  266.                                           Printing Manager changes it }
  267.     IF thePrRecHdl <> NIL THEN
  268.         BEGIN
  269.             PrOpen;
  270.                 IF (PrError = noErr) THEN
  271.                     BEGIN
  272.                         IF OKToInteract THEN
  273.                             IF PrStlDialog(thePrRecHdl) THEN
  274.                         
  275.                         { Do any post-processing on the style dialog results 
  276.                           you need to here }
  277.                           
  278.                         ELSE
  279.                             PrSetError(iPrAbort); { if we can't do the
  280.                                                     dialog, indicate that 
  281.                                                     we aborted it }
  282.                     END;
  283.             printError := PrError;
  284.  
  285.             PrClose;
  286.  
  287.             IF (printError <> noErr) AND (printError <> iPrAbort) THEN
  288.                 PostPrintingErrors(printError);
  289.         END;
  290.  
  291.     UseResFile(oldResFile);                { restore the resource file }
  292.     SetPort(oldPort);                    { restore the GrafPort }
  293.  
  294. END; {PageSetup}
  295.  
  296. {$S Print}
  297. (******************************************************************************
  298. *
  299. * Public: Print
  300. *
  301. * This routine implements the printing loop.  We always print PICTs in this
  302. * code, so we'll accept a PICT handle directly if theWindow is NIL.
  303. *
  304. * Print first saves the old resource file because the Printing Manager
  305. * changes it.  Then it gets a PICT of the document (or uses the one passed)
  306. * and counts the number of pages in it using DetermineNumberOfPagesInDoc.
  307. * After that, we update all our application windows so they don't have holes
  308. * in them while we print, and then we call PrJobDialog if OKToInteract
  309. * says interaction is allowed.  (We go ahead and print if we can't call the
  310. * job dialog to be Apple Event friendly.)  However, if we're passed a merge
  311. * print record, we call NewPrJobMerge (from Macintosh Technical Note "Fun
  312. * with PrJobMerge") to merge those parameters instead of asking the user
  313. * for new ones.
  314. *
  315. * With all the pleasantries aside, we're ready to go.  We get the number of
  316. * copies from the print record, and the requested first and last pages of
  317. * the document.  We then normalize those values and open an optional printing
  318. * status dialog.  Sample doesn't actually _have_ one of these, but as long
  319. * as it doesn't require any events all you have to do to get one is add 'DLOG'
  320. * #257 to the resource fork and it will magically appear.  If it needs event
  321. * handling, you'll have to add a pIdleProcedure to handle events in it.
  322. *
  323. * Then, for each requested copy, switch to the saved resource file, open
  324. * a document with PrOpenDoc, set the port to the returned port, open each
  325. * page and draw it.  We back out gracefully through each call.
  326. *
  327. * Note that we do _not_ check for errors in the printing loop -- you must
  328. * call each PrCloseXXX routine for every PrOpenXXX routine you call.  If you
  329. * call PrOpenPage, you _must_ call PrClosePage even if PrOpenPage returns
  330. * an error.  So we Handle errors at the end.
  331. *
  332. * After it's all looped through, we call PrPicFile if necessary, post
  333. * any printing errors and dispose of the optional status dialog if we have one.
  334. ******************************************************************************)
  335.  
  336. FUNCTION Print(thePrRecHdl: THPrint; theWindow: WindowPtr; thePict: PicHandle;
  337.                theMergeRec: THPrint): BOOLEAN;
  338.  
  339. VAR
  340.     copies,                                { count variable for copies loop }
  341.     firstPage,                            { first page to print as requested }
  342.     lastPage,                            { last page to print as requested }
  343.     numberOfCopies,                        { how many copies to print }
  344.     pageNumber,                            { count variable for pages loop }
  345.     printMgrsResFile,                    { saved Printing Mgr's res file }
  346.     realNumberOfPagesInDoc: INTEGER;    { normalized count of pages to print }
  347.     printError: OSErr;                    { errors as returned by Printing Mgr }
  348.     oldPort: GrafPtr;                    { GrafPort on entry to routine }
  349.     thePrPort: TPPrPort;                { returned by PrOpenDoc }
  350.     printingStatusDialog: DialogPtr;    { optional Status dialog Ptr }
  351.     documentBounds: Rect;                { size of entire Picture/document }
  352.     canWePrint,                            { TRUE if printing should proceed }
  353.     ignore: BOOLEAN;                    { Functions must return values in Pascal,
  354.                                           but we don't have to use them }
  355.  
  356. BEGIN
  357.     Print := FALSE;                        { assume we didn't print }
  358.     GetPort(oldPort);                    { From the Technical Note -- save port }
  359.     printingStatusDialog := NIL;        { indicate no status dialog }
  360.  
  361.     IF (MemError = noErr) AND (thePrRecHdl <> NIL) THEN
  362.     BEGIN
  363.         UnloadSeg(@DoCircleOptions);    { unload the dialogs segment }
  364.         PrOpen;
  365.         IF (PrError = noErr) THEN
  366.         BEGIN
  367.             printMgrsResFile := CurResFile;       { save the resource file }
  368.  
  369.             { if we're passed a WindowPtr, make a Picture from
  370.               that window's contents.  Otherwise, use the Picture
  371.               we're passed. }
  372.             
  373.             IF theWindow <> NIL THEN
  374.                 documentPicture := MakeDocumentPicture(DocumentPtr(GetWRefCon
  375.                                    (theWindow)))
  376.             ELSE
  377.                 documentPicture := thePict;
  378.             documentBounds := documentPicture^^.picFrame;
  379.             realNumberOfPagesInDoc :=
  380.                                  DetermineRealNumberOfPagesInDoc(documentBounds,
  381.                                                                  thePrRecHdl);
  382.  
  383.             UpdateAllAppWindows;
  384.  
  385.             canWePrint := TRUE;
  386.  
  387.             { If we have a merge record, use it and PrJobMerge instead of 
  388.               asking the user for more job values }
  389.             
  390.             IF theMergeRec = NIL THEN
  391.             BEGIN
  392.                 IF OKToInteract THEN
  393.                     canWePrint := PrJobDialog(thePrRecHdl);
  394.             END
  395.             ELSE
  396.             BEGIN
  397.                 ignore := PrValidate(thePrRecHdl);
  398.                 NewPrJobMerge(theMergeRec, thePrRecHdl);
  399.             END;
  400.  
  401.             IF canWePrint THEN
  402.             BEGIN
  403.                 numberOfCopies := thePrRecHdl^^.prJob.iCopies;
  404.  
  405.                 { If we leave the page range as it is in the dialog, the
  406.                   Printing Manager will expect us to draw all pages, and it
  407.                   will throw away the unnecessary ones.  For example, if the
  408.                   user picks pages 7-12, the Printing Manager expects us to
  409.                   print at least pages 1-12, and it will throw away pages
  410.                   1-6.  kind of ineffcient.  Instead, we take the page range
  411.                   out of the print record and reset the page range to the
  412.                   defaults, and we start printing with (say) page 7.  That
  413.                   way we only draw what needs to be printed. }
  414.  
  415.                 firstPage := thePrRecHdl^^.prJob.iFstPage;
  416.                 lastPage := thePrRecHdl^^.prJob.iLstPage;
  417.                 
  418.                 thePrRecHdl^^.prJob.iFstPage := iPrPgFst;
  419.                 thePrRecHdl^^.prJob.iLstPage := iPrPgMax;
  420.  
  421.                 IF (realNumberOfPagesInDoc < lastPage) THEN
  422.                     lastPage := realNumberOfPagesInDoc;
  423.  
  424.                 IF OKToInteract THEN
  425.                     printingStatusDialog := GetNewDialog(257,NIL, POINTER( - 1));
  426.  
  427.                 FOR copies := 1 TO numberOfCopies DO
  428.                 BEGIN
  429.  
  430.                     { If you have a pIdleProcedure, install it here like this:
  431.                      
  432.                       thePrRecHdl^^.prJob.pIdleProc :=  @MyPIdleProcedure; }
  433.                     UseResFile(printMgrsResFile);        { restore the driver's res file}
  434.                     thePrPort := PrOpenDoc(thePrRecHdl, NIL, NIL);
  435.                                                     { open a document with PrOpenDoc }        
  436.                     SetPort(GrafPtr(thePrPort));    { switch to the printing GrafPort }        
  437.     
  438.                     IF (PrError = noErr) THEN
  439.                     BEGIN                        { print the requested range of pages }
  440.                         pageNumber := firstPage;
  441.                         WHILE ((pageNumber <= lastPage) AND (PrError = noErr)) DO
  442.                         BEGIN
  443.     
  444.                             PrOpenPage(thePrPort, NIL);    
  445.                                                 { open each page with no scaling rect }                
  446.     
  447.                             IF (PrError = noErr) THEN
  448.                                 DrawStuff(thePrRecHdl^^.prInfo. rPage, GrafPtr(thePrPort),
  449.                                           pageNumber,documentPicture);
  450.                             PrClosePage(thePrPort);
  451.                             pageNumber :=pageNumber + 1;
  452.                         END;
  453.                     PrCloseDoc(thePrPort);
  454.                     END;     
  455.                 END;
  456.             END
  457.             ELSE     { or else the user cancelled }
  458.                 PrSetError(iPrAbort); 
  459.         END;
  460.  
  461.         { call PrPicFile if necessary }
  462.  
  463.         IF (thePrRecHdl^^.prJob.bJDocLoop = bSpoolLoop) AND 
  464.            (PrError =  noErr) THEN
  465.             PrPicFile(thePrRecHdl, NIL, NIL, NIL, theStatus);
  466.  
  467.         printError := PrError;        { Grab this before closing the Printing
  468.                                       Manager, when it vanishes }
  469.  
  470.         PrClose;
  471.  
  472.         { NOW we can safely report errors }
  473.  
  474.         IF (printError <> noErr) THEN
  475.             PostPrintingErrors(printError)
  476.         ELSE
  477.             Print := TRUE;
  478.             
  479.         { if we made a document Picture, we should kill it }
  480.         
  481.         IF (theWindow <> NIL) THEN
  482.             KillPicture(documentPicture);
  483.  
  484.     END;
  485.  
  486.     IF (printingStatusDialog <> NIL) THEN    { if there's a Status dialog }
  487.         DisposeDialog(printingStatusDialog); { then dispose of it }
  488.  
  489.     SetPort(oldPort);                        { restore the original GrafPort }
  490. END; { Print }
  491.